<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file contains the PDORM_Domain class.
 *
 * PHP version 5
 *
 * This file is part of PEAR::PDORM - A database object relational management
 * layer for PHP 5 built using PDO. PHP version 5.1+ is recommended.
 *
 * PEAR::PDORM is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * PEAR::PDORM is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with PEAR::PDORM. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package     PDORM
 * @category    Object-relational Management
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   2007 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: Domain.php 42 2007-12-12 04:59:11Z mjijackson $
 * @link        http://pear.php.net/packages/PDORM
 */

/**
 * The domain.
 *
 * @package     PDORM
 * @category    Object-relational Management
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
class PDORM_Domain
{

    /**
     * An array that contains all defined relations in the domain.
     *
     * @var     array
     * @access  private
     */
    private $rel_options = array();

    /**
     * An array of valid keys that may be used to define one domain class'
     * relation with another.
     *
     * @var     array
     * @access  private
     * @static
     */
    private static $valid_rel_keys = array(
        'has_one'           => array(
            'dependent',
            'foreign_key'
        ),
        'has_many'          => array(
            'dependent'
        ),
        'belongs_to'        => array(
            'foreign_key'
        ),
        'many_to_many'      => array(
            'join_table',
            'foreign_key',
            'own_foreign_key'
        )
    );

    /**
     * Constructor.
     *
     * @access  private
     */
    private function __construct() {}

    /**
     * Gets the instance of the PDORM_Domain class.
     *
     * @return  PDORM_Domain        The PDORM_Domain singleton object
     * @access  public
     * @static
     */
    public static function getInstance()
    {
        static $inst;
        if (!$inst) {
            $inst = new self();
        }
        return $inst;
    }

    /**
     * Gets an array of {@link $valid_rel_keys valid keys} that may be used to
     * define a domain class' relation with another.
     *
     * @return  array           An array of valid relation keys
     * @access  public
     * @static
     */
    public static function validRelationKeys()
    {
        return self::$valid_rel_keys;
    }

    /**
     * Tells whether or not the given domain class' relations have already been
     * defined or not.
     *
     * @param   string      $domain_cls     The domain class to check
     * @return  bool                        True if it's already set up, false
     *                                      otherwise
     * @access  public
     */
    public function isSetup($domain_cls)
    {
        return isset($this->rel_options[$domain_cls]);
    }

    /**
     * Sets up the given mapper's domain class' relations to other classes in
     * the domain.
     *
     * @param   PDORM_Mapper    $mapper     The mapper to set up the relations
     *                                      for
     * @return  void
     * @access  public
     */
    public function setupRelations($mapper)
    {
        $domain_cls = $mapper->domainClass();
        if ($this->isSetup($domain_cls)) {
            return;
        }

        $rel_options = $mapper->getRelations(); // should be raw relation data
        $this->rel_options[$domain_cls] = array();

        foreach (array_keys(self::$valid_rel_keys) as $rel_array) {
            $valid_keys = array_merge(PDORM_Mapper::validFindKeys(),
                self::$valid_rel_keys[$rel_array]);

            foreach ($rel_options[$rel_array] as $rel => $options) {
                if (!is_array($options)) {
                    $rel = $options;
                    $options = array();
                }

                // make sure keys are valid
                PDORM_assert_valid_keys($options, $valid_keys);

                $rel = strtolower($rel);
                $this->rel_options[$domain_cls][$rel] = array();

                switch ($rel_array) {
                    case 'has_one':
                        $this->setForeignKey($domain_cls, $rel, $options);
                        $this->setDependent($domain_cls, $rel, $options);
                    break;
                    case 'has_many':
                        $this->setDependent($domain_cls, $rel, $options);
                    break;
                    case 'belongs_to':
                        $this->setForeignKey($domain_cls, $rel, $options);
                    break;
                    case 'many_to_many':
                        $this->setForeignKey($domain_cls, $rel, $options);
                        $this->setOwnForeignKey($domain_cls, $rel, $options);
                        $this->setJoinTable($mapper, $rel, $options);
                    break;
                }

                $this->setFindOptions($domain_cls, $rel, $options);
            }
        }
    }

    /**
     * Gets an array of relation data for the given domain class.
     *
     * @param   string      $domain_cls     The domain class name
     * @return  array                       An array of the given class'
     *                                      relation data
     * @access  public
     */
    public function getRelations($domain_cls)
    {
        return $this->isSetup($domain_cls) ? $this->rel_options[$domain_cls] : null;
    }

    /**
     * Sets the dependency relationship between this mapper's object class and
     * another. If the specified object class is dependent on this one, its
     * records will be deleted from the database when this one is.
     *
     * @param   string  $rel        The name of the related object class
     * @param   array   $options    The relation options
     * @return  void
     * @access  private
     */
    private function setDependent($domain_cls, $rel, $options)
    {
        $this->rel_options[$domain_cls][$rel]['dependent'] = (isset($options['dependent'])
            && is_bool($options['dependent']))
            ? $options['dependent']
            : false;
    }

    /**
     * Sets the name of the foreign key to use in this mapper for another
     * object class.
     *
     * @param   string  $rel        The name of the related object class
     * @param   array   $options    The relation options
     * @return  void
     * @access  private
     */
    private function setForeignKey($domain_cls, $rel, $options)
    {
        $this->rel_options[$domain_cls][$rel]['foreign_key'] = !empty($options['foreign_key'])
            ? $options['foreign_key']
            : PDORM_camel2uscore($rel) . '_id';
    }

    /**
     * Sets the name of the foreign key to use for this mapper's object class
     * in the association table that links it to the related object class.
     *
     * @param   string  $rel        The name of the related object class
     * @param   array   $options    The relation options
     * @return  void
     * @access  private
     */
    private function setOwnForeignKey($domain_cls, $rel, $options)
    {
        $this->rel_options[$domain_cls][$rel]['own_foreign_key'] = !empty($options['own_foreign_key'])
            ? $options['own_foreign_key']
            : PDORM_camel2uscore($domain_cls) . '_id';
    }

    /**
     * Sets the name of the join table between this mapper's table and the
     * table of a related object class.
     *
     * @param   string  $rel        The name of the related object class
     * @param   array   $options    The relation options
     * @return  void
     * @access  private
     */
    private function setJoinTable($mapper, $rel, $options)
    {
        $domain_cls = $mapper->domainClass();
        if (!empty($options['join_table'])) {
            $this->rel_options[$domain_cls][$rel]['join_table'] = $options['join_table'];
        } else {
            $table_one = $mapper->tableName();
            $rel_mapper = PDORM::mapperFactory($rel);
            $table_two = $rel_mapper->tableName();
            $this->rel_options[$domain_cls][$rel]['join_table'] = (strcmp($table_one, $table_two) < 0)
                ? $table_one . '_' . $table_two
                : $table_two . '_' . $table_one;
        }
    }

    /**
     * Sets the custom find options to use for a related object class.
     *
     * @param   string  $rel        The name of the related object class
     * @param   array   $options    The relation options
     * @return  void
     * @access  private
     */
    private function setFindOptions($domain_cls, $rel, $options)
    {
        $this->rel_options[$domain_cls][$rel]['find'] = $options;
    }

}

?>
